package org.example.aop;

/**
 * @author liyishan
 * @date 2024/1/10 14:27
 * @apiNote
 */

import org.example.annotation.TimeStatisticsLimit;
import org.example.exception.RateLimitException;
import org.example.properties.RateLimitProperties;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * Redis普通时间统计限流切面
 * @author Mr.Tao
 */
public class TimeStatisticsLimitAspect implements MethodBeforeAdvice {
    private final RateLimitProperties limitProperties;
    private final RedisTemplate<String, Long> redisTemplate;

    private final DefaultRedisScript<Long> redisLuaScript;

    public TimeStatisticsLimitAspect(RedisTemplate<String, Long> redisTemplate, DefaultRedisScript<Long> redisLuaScript, RateLimitProperties limitProperties) {
        this.redisTemplate = redisTemplate;
        this.redisLuaScript = redisLuaScript;
        this.limitProperties = limitProperties;
    }

    @Override
    public void before(Method method, Object[] args, Object target) {
        // 获取LeakyBucketLimit注解，如果方法和类上同时出现，以方法上的为准
        Class<?> clazz = method.getDeclaringClass();
        TimeStatisticsLimit timeStatisticsLimit;
        timeStatisticsLimit = method.getAnnotation(TimeStatisticsLimit.class);
        if (timeStatisticsLimit == null) {
            timeStatisticsLimit = clazz.getAnnotation(TimeStatisticsLimit.class);
            if (timeStatisticsLimit == null) {
                return;
            }
        }

        // 根据用户IP（可选）和接口方法，构造key
        String key = limitProperties.getKey(timeStatisticsLimit.value(), timeStatisticsLimit.key(), clazz.getSimpleName(), method.getName(), limitProperties.getStrategyConfig().getTimeStatisticsLimitStrategy());

        //创建单个元素的List集合 这个方法主要用于只有一个元素的优化，减少内存分配，无需分配额外的内存
        List<String> keys = Collections.singletonList(key);
        /*
         * 通过redisTemplate来执行lua脚本
         *      参数1：lua脚本
         *      参数2：redis中存储的与接口名称相关的key
         *      参数3：单位时间内的限流次数
         *      参数4：限流的单位时间
         */
        Long number = redisTemplate.execute(redisLuaScript, keys, timeStatisticsLimit.count(), timeStatisticsLimit.time());
        if (number != null && number.intValue() != 0 && number.intValue() <= timeStatisticsLimit.count()) {
            return;
        }
        throw new RateLimitException("请求过于频繁，请稍后重试！");
    }


}
